Форум по Delphi программированию

Delphi Sources



Вернуться   Форум по Delphi программированию > Разное > Исходники и статьи
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #1  
Старый 05.12.2011, 19:56
Sn0wSky Sn0wSky вне форума
Прохожий
 
Регистрация: 04.12.2011
Сообщения: 8
Репутация: 10
Сообщение Простейшая онлайн рисовалка для новичков через сокеты

Всегда тянуло к програмкам хоть как-то связанным с взаимодействием пользователей. Первое и самое простенькое, что решил написать, это чат на сокетах, погуглил, благо в интернете много гайдов по этой теме, написал. Чат был ужасно примитивным, поэтому, ради практики, решил немного увеличить его функционал, введя список пользователей, небольшой фильтр мата и тому прочее. Это всё делалось не ради чего-то высшего, а ради себя. И вот между тем решил еще чуть более усложнить программку и рядом с самим окном чата сделать небольшую стену, для общего рисования, где можно творить всем и сразу.
О том как сделать сам чат говорить не буду, т.к. а) В интернете очень много гайдов по этой теме б) Первая программа которую я написал на сокетах и был тот самый чат по тем самым гайдам, и оттого работа рисовалки очень схожа с работой чата. Программа будет с очень бедным функционалом, только окошко для рисования, никакого списка юзеров и т.п..
И так!
Будем писать и сервер, и клиент, причем второй получился у меня более сложный по содержанию, поэтому начнём пожалуй с сервачка
От сервера нам многое и не надо. Он всего-то и нужен нам для того, чтобы рассылать полученные координаты всем остальным юзверям, ну и естественно работал, поэтому из компонентов понадобится только две кнопки ( для запуска и остановки ), любая компонента для ввода порта, я взял TEdit, ну и сердце нашего сервера ServerSocket ( кидайте куда угодно, в готовой скомпилированной программе Вы его не увидите) . Естественно в коде можно забить один порт без права его ввода, но давайте лучше дадим свободы.

Ну, с интерфейсом справились.
Перейдем к самому коду. Тут писать много не надо. Запуск сервера осуществляется по такой процедуре.
Код:
procedure TForm1.Button1Click(Sender: TObject);
var port,err:integer;
begin
val(edit1.text,port,err);
ServerSocket1.Port:=port;
ServerSocket1.Active:=True;
end;
Т.е. берем порт, из TEdit, преобразуем из строки в число, указываем его серверу и запускаем. Из этого кода будет интуитивно понятно, что же будет происходить по нажатию второй кнопки.
Код:
procedure TForm1.Button2Click(Sender: TObject);
begin
ServerSocket1.Active:=False;
end;
На этом отложим наш сервер на недолгое время. Перейдем к клиенту. Тут, как я говорил раннее всё сложнее, нам понадобятся: две кнопки ( для коннекта и очистки окна ), ClientSocket, TImage ( ну или любой другой объект с канвой ), два TEdit ( один для IP, второй для порта)

Не красота конечно, ну ладно, переживём. Первое что надо сделать это коннект:
Код:
procedure TForm1.Button2Click(Sender: TObject);
var port,err:integer;
begin
ClientSocket1.Host:=Edit1.text;
val(edit2.text,port,err);
ClientSocket1.Port:=port;
ClientSocket1.Open;
end;
Ip-адресс записывается как строка, поэтому переводить его не надо, а вот порт у нас целое число, поэтому пользуемся VAL. Ну вот, мы на сервере, если клиент не может присоединится к серверу ( не тот порт, сервер не работает и т.д. и т.п. ), то он вам обязательно об этом сообщит, а если по нажатию на кнопку Connect ничего не произошло, то это хороший знак. Ладно, мы на сервере, что дальше? Дальше самое интересное. На повестке дня три процедуры связанные с Image1 ( возможно дальше пойдёт мутный способ, и можно было обойтись легче, но моя голова решила так )

Код:
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
moused:=True;
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
moused:=False;
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var x1,y1:string;
var send:string;
begin
if moused then
begin
str(x,x1);
str(y,y1);
send:=x1+' '+y1;
ClientSocket1.Socket.SendText(send);
end;
end;
Мы же не хотим чтобы во время любого движения мыши по Image1 у нас что-то рисовалось, нам же надо кнопочку зажать! Для этого вводим глобальную переменную ( у меня moused ) и при нажатии левой кнопкой мыши по Image1, делаем moused:=True, при отжатии соответственно False. Далее идем в процедуру по движению мыши. Проверяем, если ЛКМ зажата, то начинаем яростно слать координаты, преобразуя их в одну строку, где координаты разделены пробелом. Возвращаемся к серверу и получаем наши месседжи и возвращаем их обратно всем клиентам. Для получения сокета есть подходящее событие - OnSocketRead. Вот и добавляем на нашем сервере такую процедуру выполняущуюся по данному событию:
Код:
procedure TForm1.ServerSocket1ClientWrite(Sender: TObject;
Socket: TCustomWinSocket);
var koordinati:string;
var i:integer;
begin
koordinati:=Socket.ReceiveText;
for i:=0 to ServerSocket1.Socket.ActiveConnections-1 do
ServerSocket1.Socket.Connections[i].SendText(Socket.ReceiveText);
end;
Сервер получает координаты и рассылает их дальше всем клиентам. ServerSocket1.Socket.ActiveConnections возвращает количество активных клиентов, но нумерация в ServerSocket1.Socket.Connetions начинается с нуля, поэтому в цикле перебор начинаем с нуля и до (кол-во клиентов-1). Отлично, теперь осталось получить и обработать со стороны клиента пришедшие координаты. Опять возвращаемся к нашему клиенту. Пишем процедуру для ClientSocket по событию OnRead:
Код:
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var textik:string;
x1,y1,i,err,j:integer;
koor:string;
begin
textik:=Socket.ReceiveText;
for i:=1 to length(textik) do begin
   if textik[i]<>' ' then koor:=koor+textik[i]
   else if textik[i]=' ' then
                 begin val(koor,x1,err);
                 break; 
                 end;
                                          end;
                 koor:='';
for j:=i+1 to length(textik) do
koor:=koor+textik[j];
val(koor,y1,err);
Image1.Canvas.Pixels[x1,y1]:=clBlack
end;
Чуть больше кода чем раньше, но тут все просто, из нашей строки достаем координату Х и Y, и ставим черную точку в то место, куда они указывают. Отрисовка по точкам конечно сильно страдает, очень много координаты идёт на сервер, не все успевают обрабатываться и высылаться, по-этому лучше бы было сделать через LineTo, но там появляются другие проблемы, с тем, чтобы для LineTo нужна начальная координата и более сложный код отправки и получения координат, т.к. если делать как у нас, то при рисовании двух и более клиентов, будут соединятся все точки, и получится кошмар. Да, это можно быстренько осуществить, но сейчас мы особо не заморачиваемся. Кнопка очистки пишется элементарно, мы заместо координат отправляем какое-нибудь определенное сообщение, например "clear", оно возвращается клиентам, и прежде чем что-то рисовать, проверяем, если вернувшаяся строка равна "clear", то очищаем наш Image1, иначе дальше отрисовываем пиксели.
И так, вроде бы всё готово, компилируем обе программы, запускаем сервер, вводим порт, жмем старт, запускаем клиент, вводим необходимые данные и коннектимся. Тадааам!


http://justforfun27.livejournal.com/
Изображения
Тип файла: jpg 1.jpg (13.0 Кбайт, 26 просмотров)
Тип файла: jpg 2.jpg (88.9 Кбайт, 22 просмотров)

Последний раз редактировалось Sn0wSky, 05.12.2011 в 19:58.
Ответить с цитированием
  #2  
Старый 05.12.2011, 20:39
Аватар для Amgsys HQ
Amgsys HQ Amgsys HQ вне форума
Начинающий
 
Регистрация: 11.11.2011
Адрес: Загрузочный сектор
Сообщения: 115
Версия Delphi: 7, XE
Репутация: 538
По умолчанию

Я тут подумал, может лучше было сделать через массив?
Поясню: когда рисуешь (клиент), заносишь координаты в нейкий массив, а после того, как отпустил клавишу (или другой вариант - нажал на кнопку), этот массив передал на сервер. Вообщем, чтобы сеть не нагружать. Представь, линия из 100 пикселей = 100 сет. пакетов...
__________________
Работа пpогpаммиста и шамана имеет очень много общего:
оба боpмочyт непонятные слова, совеpшают непонятные действия и не могyт объяснить, как оно pаботает.
Ответить с цитированием
  #3  
Старый 05.12.2011, 21:56
Sn0wSky Sn0wSky вне форума
Прохожий
 
Регистрация: 04.12.2011
Сообщения: 8
Репутация: 10
Сообщение

Цитата:
Сообщение от Amgsys HQ
Я тут подумал, может лучше было сделать через массив?
Поясню: когда рисуешь (клиент), заносишь координаты в нейкий массив, а после того, как отпустил клавишу (или другой вариант - нажал на кнопку), этот массив передал на сервер. Вообщем, чтобы сеть не нагружать. Представь, линия из 100 пикселей = 100 сет. пакетов...

Хм, верно блин, просто моя голова пока плохо думает в плане оптимизации.
Спасибо)
Ответить с цитированием
  #4  
Старый 05.12.2011, 22:13
Аватар для Bargest
Bargest Bargest вне форума
Профессионал
 
Регистрация: 19.10.2010
Адрес: Москва
Сообщения: 2,390
Версия Delphi: XE3/VS12/FASM
Репутация: 14665
По умолчанию

Зато при таком подходе будет рисоваться прямо "на ходу", то есть как один юзер мышь ведет, так и рисуется. Если же делать через массив, то пока чел не отпустит, никто ничего не увидит. А если он хочет в одно движение нарисовать какую-то фигуру длинную?
Думаю, хорошее решение как всегда где-то посередине: накапливать значения и по истечении какого-то времени передавать собранные данные. Тогда у пользователей будет рисоваться немного дерганнее, чем должно быть, но все же почти сразу, и нагрузка на сеть тоже будет поменьше. Время сбора данных можно варьировать, тем самым менять нагрузку, и опытным путем определить соотношение "нагрузка/скорость прорисовки у клиентов".
Ну и разумеется, если пользователь отпустил мышу, то больше ждать нечего - надо отправлять то, что есть.
__________________
jmp $ ; Happy End!
The Cake Is A Lie.

Последний раз редактировалось Bargest, 05.12.2011 в 22:15.
Ответить с цитированием
  #5  
Старый 05.12.2011, 23:05
Sn0wSky Sn0wSky вне форума
Прохожий
 
Регистрация: 04.12.2011
Сообщения: 8
Репутация: 10
По умолчанию

Цитата:
Сообщение от Bargest
Зато при таком подходе будет рисоваться прямо "на ходу", то есть как один юзер мышь ведет, так и рисуется. Если же делать через массив, то пока чел не отпустит, никто ничего не увидит. А если он хочет в одно движение нарисовать какую-то фигуру длинную?
Думаю, хорошее решение как всегда где-то посередине: накапливать значения и по истечении какого-то времени передавать собранные данные. Тогда у пользователей будет рисоваться немного дерганнее, чем должно быть, но все же почти сразу, и нагрузка на сеть тоже будет поменьше. Время сбора данных можно варьировать, тем самым менять нагрузку, и опытным путем определить соотношение "нагрузка/скорость прорисовки у клиентов".
Ну и разумеется, если пользователь отпустил мышу, то больше ждать нечего - надо отправлять то, что есть.
Да, в принципе можно даже по 10 точек отсылать, глаз не будет коробить сильно, да и запросов в 10 раз меньше, хорошая идея.

Мне по поводу сокетов вот что интересно, есть какой-нибудь уникальный идентификатор у каждого клиента?
Просто пока не придумал появилась идея самому раздавать номера и отправлять клиентам, чтобы там в переменную забивалось и сидела, при этом к каждому обращению к серверу еще и номер отправлять, но муторно как-то, в дельфи что-нибудь такое не предусмотрено?
Ответить с цитированием
  #6  
Старый 06.12.2011, 14:53
Аватар для Bargest
Bargest Bargest вне форума
Профессионал
 
Регистрация: 19.10.2010
Адрес: Москва
Сообщения: 2,390
Версия Delphi: XE3/VS12/FASM
Репутация: 14665
По умолчанию

Уникальный идентификатор есть - называется "IP + Port". =) Иначе как бы сервер знал, кому передавать данные? Вытаскиваешь из сокета IP и порт - вот тебе идентификатор. Правда клиент этих параметров может не знать (если он стоит за роутером). Но сервер будет знать и всегда уникально для каждого клиента.
__________________
jmp $ ; Happy End!
The Cake Is A Lie.
Ответить с цитированием
  #7  
Старый 06.12.2011, 23:52
Sn0wSky Sn0wSky вне форума
Прохожий
 
Регистрация: 04.12.2011
Сообщения: 8
Репутация: 10
По умолчанию

Цитата:
Сообщение от Bargest
Уникальный идентификатор есть - называется "IP + Port". =)

То, что надо, спасибо, будем развиваться.
Ответить с цитированием
Ответ


Delphi Sources

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

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 12:38.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources" by BrokenByte Software, 2004-2023

ВКонтакте   Facebook   Twitter